#include "minicon.h"
#include <stdarg.h>

#if defined(_WIN64) || defined(_DEBUG) || !defined(_M_IX86)
static DWORD __forceinline lldiv( __int64 &dividend, DWORD divisor )
{ DWORD reminder = (DWORD)(dividend % divisor); dividend /= divisor; return reminder; }
#else
DWORD __fastcall lldiv( __int64 &dividend, DWORD divisor );
#endif

//extern int err_ret(int errcode, const char *msg, const WCHAR *name);

//===============================================================================================

static struct IOH {
   const WCHAR *fnm;
   HANDLE  fh;
   UINT    cp;
   bool    bConsole;
   bool    cp_set;
   bool    eof;
   __int64 total;
   BYTE   *buffer;
   int     cb, c0;
} _out, _err, _inp;

static void setFH(IOH &ios, HANDLE fh, bool inp = false)
{
   DWORD dw;
   ios.fh = fh;
   if (!inp) ios.bConsole = TRUE == ::GetConsoleMode(fh, &dw);
   else {
      ios.bConsole = TRUE == ::FlushConsoleInputBuffer(fh);
      if (ios.bConsole)
         ::SetConsoleMode(fh, ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
   }
   ios.cp = ios.bConsole ? 1200 : ios.cp_set ? ios.cp : CP_ACP;
   ios.total = 0;
   ios.cb = ios.c0 = 0; ios.eof = false;
}

bool Con::getRawFH( HANDLE& fhi, HANDLE& fho )
{
   bool res = false;
   if ( !_out.bConsole
     && !_inp.bConsole
     &&  _out.cp == _inp.cp
   ) {
      fhi = _inp.fh; fho = _out.fh; res = true;
   }
   return res;
}

//===============================================================================================

static const WCHAR s_std[] = L"-";

void Con::init()
{
   _out.fnm    = _err.fnm    = _inp.fnm    = s_std;
   _out.cp_set = _err.cp_set = _inp.cp_set = false;
   _out.total  = _err.total  = _inp.total  = 0; // buffered mode only...
   setFH(_out, ::GetStdHandle(STD_OUTPUT_HANDLE));
   setFH(_err, ::GetStdHandle(STD_ERROR_HANDLE));
   setFH(_inp, ::GetStdHandle(STD_INPUT_HANDLE), true);
}

//-----------------------------------------------------------------------------------------------

bool Con::setInp( const WCHAR *fnm )
{
   if (NULL == fnm) {
      BOOL res = ::CloseHandle(_inp.fh); _inp.fh = INVALID_HANDLE_VALUE;
      return res != FALSE;
   }
   HANDLE fh = (fnm[0] == L'-' && !fnm[1]) 
     ?  ::GetStdHandle(STD_INPUT_HANDLE)
     :  ::CreateFileW(fnm, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL
   );
   if (INVALID_HANDLE_VALUE == fh) return false;
   setFH(_inp, fh, true);
   _inp.fnm = fnm;
   return true;
}

//-----------------------------------------------------------------------------------------------

static void get_cp( int& cpo, const WCHAR * &cps )
{
   if      ( L'a' == *cps || L'A' == *cps ) cpo = CP_ACP;   // ANSI
   else if ( L'o' == *cps || L'O' == *cps ) cpo = CP_OEMCP; // OEM
   else if ( L'w' == *cps || L'W' == *cps ) cpo = 1251;     // WIN-1251
   else if ( L'd' == *cps || L'D' == *cps ) cpo = 866;      // DOS
   else if ( L'k' == *cps || L'K' == *cps ) cpo = 20866;    // KOI8-r
   else if ( L'e' == *cps || L'E' == *cps ) cpo = 1252;     // western Europe
   else {
      UINT cp = 0, base = 10, dig;
      if ( L'0' == *cps )
      { ++cps; base = 8; if ( L'x' == *cps || L'X' == *cps ) { ++cps; base = 16; } }
      for (;;) {
         dig = *cps;
         if ( dig >= L'a' && dig <= L'f' ) dig += (UINT)(L'A' - L'a');
         if ( dig >= L'A' && dig <= L'F' ) dig += (UINT)(L'9' + 1 - L'A');
         dig -= L'0';
         if ( dig < base ) { cp = base*cp + dig; ++cps; } else break;
      }
      cpo = (int)cp;
   }
   while (*cps && L',' != *cps) ++cps;
}

void Con::setCP( const WCHAR *cps )
{
   int cpo = -1, cpi = -1, cpe = -1;

   if ( L',' != *cps )
      get_cp(cpo, cps);
   if ( L',' == *cps )
      { cpi = cpo; ++cps; get_cp(cpo, cps); }
   if ( L',' == *cps )
      { ++cps; get_cp(cpe, cps); }

   if (cpo >= 0) { _out.cp = (UINT)cpo; _out.cp_set = true; }
   if (cpe >= 0) { _err.cp = (UINT)cpe; _err.cp_set = true; }
   if (cpi >= 0) { _inp.cp = (UINT)cpi; _inp.cp_set = true; }
}

//===============================================================================================
static bool _err_out = false;

bool Con::err( bool err_mode )
{ bool ret = _err_out; _err_out = err_mode; return ret; }

//-----------------------------------------------------------------------------------------------

static int out( const void *pv, int nb, IOH &outs )
{
   if (nb <= 0) return nb;
   DWORD dw = 0;
   BOOL res = ::WriteFile(outs.fh, pv, (DWORD)nb, &dw, NULL);
   if (!res)
      return -25;
   else
      return (int)dw;
}

//-----------------------------------------------------------------------------------------------
void Con::flush( int what )
{
   if ( what == 'i' ) what = 0x01; 
   if ( what == 'o' ) what = 0x02;
   if ( what == 'e' ) what = 0x04;

   if ( what & 0x01 ) {
      if (_inp.bConsole)
         ::FlushConsoleInputBuffer(_inp.fh);
      else if (_inp.fh == ::GetStdHandle(STD_INPUT_HANDLE))
      { LONG L0 = 0; ::SetFilePointer(_inp.fh, 0, &L0, FILE_BEGIN); }
      else
         setInp(NULL);
      _inp.cb = _inp.c0 = 0; _inp.eof = false;
   }
   if ( what & 0x02 ) {
      if (!_out.bConsole && _out.cb > 0) out(_out.buffer, _out.cb, _out);
      _out.cb = 0;
   }
   if ( what & 0x04 ) {
      if (!_err.bConsole && _err.cb > 0) out(_err.buffer, _err.cb, _err);
      _err.cb = 0;
   }
}

//-----------------------------------------------------------------------------------------------
static int write_( IOH& outs, const char *buf, int cb_ );

int Con::o( const char *ps, int nb )
{
   if (nb < 0) nb = ::lstrlenA(ps);
   IOH &outs = _err_out ? _err : _out;

   if ( !outs.bConsole && outs.cp == CP_ACP ) {
      return write_(outs, ps, nb);
   }

   WCHAR sw[4*1024/2 - 32];
   int no = 0, n1, nw, rc;
   while ( nb > 0 ) {
      n1 = idim(sw); if (n1 > nb) n1 = nb;
      nw = Win::atow(ps, n1, sw, (int)sizeof(sw), CP_ACP);
      rc = write(sw, nw);
      if (rc >= 0 && rc != nw)
         rc = -25; // write error
      if (rc < 0)
         return rc;
      no += nw;
      nb -= n1; ps += n1;
   }

   if ( _err_out ) flush('e');
   return no;
}

//-----------------------------------------------------------------------------------------------

int Con::o( const WCHAR *pw, int nw )
{
   if (nw < 0) nw = ::lstrlenW(pw);
   int no =  write(pw, nw);
   if ( _err_out ) flush('e');
   return no;
}

//-----------------------------------------------------------------------------------------------

int Con::o( const char cc )     { return o(&cc,   1); }
int Con::o( const int ii )      { return f("%d", ii); }
int Con::o( const unsigned ui ) { return f("%u", ui); }

int Con::o( const __int64 iL, int width )
{
   char ss[32]; int i = (int)sizeof(ss); __int64 i64 = iL;
   do { ss[--i] = (char)(lldiv(i64, 10) + '0'); } while ( i64 );
   while ( width && i+width > (int)sizeof(ss) ) ss[--i] = ' ';
   return o(ss+i, (int)sizeof(ss) - i);
}

//-----------------------------------------------------------------------------------------------

int __cdecl Con::f( const char *fmt, ... )
{
   char sa[1024*4 - 64];
   va_list va;
   va_start(va, fmt);
   int nb = wvsprintfA(sa, fmt, va);
   va_end(va);
   return o(sa, nb);
}

//-----------------------------------------------------------------------------------------------

int __cdecl Con::f( const WCHAR *fmt, ... )
{
   WCHAR sw[1024*4/2 - 32];
   va_list va;
   va_start(va, fmt);
   int nw = wvsprintfW(sw, fmt, va);
   va_end(va);
   return o(sw, nw);
}

//-----------------------------------------------------------------------------------------------

void Con::n( int n ) { while( --n >= 0 ) o("\r\n", 2); }

//===============================================================================================

int Con::confirm( const char *question, int def_answer )
{
   Con::f("%s? (y/n/Y/N)", question);
   if ( def_answer > ' ' ) { Con::o(' '); Con::o((char)def_answer); }
   Con::o(": ");
   if ( 'Y' == def_answer || 'N' == def_answer )
   { Con::f("%c\r\n", def_answer); return def_answer; }

   WCHAR sw[100];
   int nr = read(sw, 100), answer = def_answer;
   if (nr > 0 && (sw[0]==L'n' || sw[0]==L'N' || sw[0]==L'y' || sw[0]==L'Y'))
      answer = (int)sw[0];

   if ( !_inp.bConsole )
   { Con::f("%c\r\n", def_answer); return def_answer; }
   return answer;
}

//-----------------------------------------------------------------------------------------------

static const int iBuffSize = 32 * 1024;

static int write_(IOH &outs, const char *buf, int cb_ )
{
   Win::alloc((void* &)outs.buffer, iBuffSize);

   int cb = cb_; outs.total += cb;
   while ( cb > 0 ) {
      int nfree = iBuffSize - outs.cb;
      if (nfree <= 0) {
         int rc = out(outs.buffer, outs.cb, outs);
         if (rc < 0)
            return rc;
         outs.cb = 0; nfree = iBuffSize;
      }
      int nm = cb; if (nm > nfree) nm = nfree;
      Win::bmov(outs.buffer + outs.cb, buf, nm);
      buf += nm; cb -= nm;
      outs.cb += nm;
   }
   return cb_;
}

int Con::write( const WCHAR *pw, int cw )
{
   IOH &outs = _err_out ? _err : _out;
   int rc = 0;
   if (outs.bConsole) {
      DWORD dw = 0;
      ::WriteConsoleW(outs.fh, pw, (DWORD)cw, &dw, NULL);
      return (int)dw;
   }

   if (outs.cp == 1200) {
      if (outs.total == 0) rc = write_(outs, "\xff" "\xfe", 2);
      if (rc >= 0) rc = write_(outs, (const char *)pw, cw*2);
   }
   else if (outs.cp == 1201) {
      if (outs.total == 0) write_(outs, "\xfe" "\xff", 2);
      WCHAR sw[4*1024/2 - 32];
      for (int i = 0; i < cw; ) {
         int nw = idim(sw); if (i+nw > cw) nw = cw - i;
         Win::bswap(sw, nw, pw+i);
         rc = write_(outs, (const char *)sw, nw*2);
         if (rc < 0) break;
         i += nw;
      }
   }
   else {
      if (outs.total==0 && outs.cp==CP_UTF8) write_(outs, "\xef" "\xbb" "\xbf", 3);
      char ss[1024*4 - 64];
      for (int i = 0; i < cw; ) {
         int nw = (int)sizeof(ss)/4; if (i + nw > cw) nw = cw - i;
         int nc = Win::wtoa(pw+i, nw, ss, (int)sizeof(ss), outs.cp);
         rc = write_(outs, ss, nc);
         if (rc < 0) break;
         i += nw;
      }
   }
   return rc < 0 ? rc : cw;
}

bool Con::setOut( const WCHAR *fnm )
{
   if (NULL == fnm) {
      flush('o');
      ::SetEndOfFile(_out.fh);
      ::CloseHandle(_out.fh);
      _out.fh = INVALID_HANDLE_VALUE;
      _out.fnm = fnm;
      return true;
   }
   HANDLE fh = ::CreateFileW(fnm,
      GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL
   );
   if (INVALID_HANDLE_VALUE == fh) return false;
   setFH(_out, fh);
   _out.fnm = fnm;
   return true;
}

//-----------------------------------------------------------------------------------------------

int Con::read(WCHAR *buf, int cw )
{
   if (_inp.bConsole) {
      DWORD nr = 0;
      if (!_inp.eof) {
         ::ReadConsoleW(_inp.fh, buf, cw, &nr, NULL);
         for (DWORD i=0; i < nr; ++i) if (buf[i] == L'\x1a') { _inp.eof = true; nr = i; break; }
      }
      return (int)nr;
   }
   Win::alloc((void* &)_inp.buffer, iBuffSize);

   int nb, nw;
   if ((nb = _inp.cb - _inp.c0) < 8 && !_inp.eof) { // try to read
      if (_inp.c0 > 0 && nb > 0)
         Win::bmov(_inp.buffer, _inp.buffer+_inp.c0, nb);
      _inp.c0 = 0;
      _inp.cb = nb;
      DWORD dw = 0;
      BOOL res = ::ReadFile(_inp.fh, _inp.buffer+nb, (DWORD)(iBuffSize-nb), &dw, NULL);
      _inp.eof = dw == 0;
      if ( !res && ::GetLastError() != ERROR_BROKEN_PIPE )
         return -26;

      _inp.cb += (int)dw;
      if (_inp.total == 0 && dw >= 2) {
         BYTE c0 = _inp.buffer[0], c1 = _inp.buffer[1], c2 = _inp.buffer[2];
         if (c0 == 0xff && c1 == 0xfe && !_inp.cp_set || _inp.cp == 1200) {
            _inp.c0 += 2; _inp.cp = 1200;
         }
         else if (c0 == 0xfe && c1 == 0xff && !_inp.cp_set || _inp.cp == 1201) {
            _inp.c0 += 2; _inp.cp = 1201;
         }
         else if (dw >= 3 && c0 == 0xef && c1 == 0xbb && c2 == 0xbf && !_inp.cp_set || _inp.cp == 65001) {
            _inp.c0 += 3; _inp.cp = 65001;
         }
         if (!_out.cp_set && !_out.bConsole)
         { _out.cp = _inp.cp; _out.cp_set = true; }
      }
      _inp.total += dw;
   }
   if ((nb = _inp.cb - _inp.c0) < 1)
      return 0;

   if ( _inp.cp == 1200 ) {
      nw = nb / 2; if (nw > cw)  nw = cw;
      Win::bmov(buf, _inp.buffer + _inp.c0, nw * 2);
      _inp.c0 += nw * 2;
      return nw;
   }
   else if ( _inp.cp == 1201 ) {
      nw = nb / 2; if (nw > cw)  nw = cw;
      Win::bswap(buf, nw, (const WCHAR *)(_inp.buffer + _inp.c0));
      _inp.c0 += nw * 2;
      return nw;
   }
   else {
      nw = Win::atow((const char *)_inp.buffer + _inp.c0, _inp.cb-_inp.c0, buf, cw, _inp.cp);
      if (nw > 0) nb = Win::wtoa(buf, nw, NULL,0, _inp.cp);
      else { nw = 1; *buf = L'?'; nb = 1; }
      _inp.c0 += nb;
      return nw;
   }
}

//===============================================================================================
